package metadata;

import java.util.*;

import utility.DDLException.*;

public class Database {
	
	//private vars
	private String 					name		= "";
	private Map<String, Table> 		tables 		= null;
	private Map<String, Key> 		keys		= null;
	private Map<String, ForeignKey> foreignKeys	= null;
	
	//constructors
	/**
	 * Creates a new Database object with the given name. <br>
	 * <br>
	 * Can exist with 0 tables, keys and foreign keys.<br>
	 * Chosen implementations for collections are:
	 * <ul>
	 * <li>tables - HashMap</li>
	 * <li>keys - HashMap</li>
	 * <li>foreignKeys - HashMap</li>
	 * </ul>
	 * Any and all getX(String key) and hasX(String key) methods will be directly<br>
	 * calling get(String key) and contains(String key) respectively of the HashMap objects<br>
	 * 
	 * @param name The name of the database.
	 */
	public Database(String name) {
		this.name 	= name;
		
		tables 		= new HashMap<String, Table>();
		keys 		= new HashMap<String, Key>();
		foreignKeys	= new HashMap<String, ForeignKey>();
	}
	
	
	//simple getters
	public String getName() { return name; }
	
	
	//simple setters
	public void setName(String name) { this.name = name; }
	
	
	//complex getters
	/**
	 * Returns true if the database contains the table of which the name is given. <br>
	 * 
	 * @param tableName Name of the table to search for.
	 * @return True if table name exists, otherwise false.
	 */
	public boolean hasTable(String tableName) {
		return tables.containsKey(tableName);
	}

	/**
	 * Returns true if the database contains the key of which the name is given. <br>
	 * 
	 * @param keyName Name of the key to search for.
	 * @return True if key name exists, otherwise false.
	 */
	public boolean hasKey(String keyName) {
		return keys.containsKey(keyName);
	}
	
	/**
	 * Returns true if the database contains the foreign key of which the name is given. <br>
	 * 
	 * @param foreignKeyName Name of the foreign key to search for.
	 * @return True if foreign key name exists, otherwise false.
	 */
	public boolean hasForeignKey(String foreignKeyName) {
		return foreignKeys.containsKey(foreignKeyName);
	}
	
	/**
	 * Returns the table corresponding to the given name. <br>
	 * <br>
	 * Returns null if non existing table is requested.<br>
	 * 
	 * @param tableName The name of the table to return.
	 * @return Returns the table corresponding to the given name.
	 */
	public Table getTable(String tableName) {
		return tables.get(tableName);
	}

	/**
	 * Returns the key corresponding to the given name. <br>
	 * <br>
	 * Returns null if non existing key is requested.<br>
	 * 
	 * @param keyName The name of the key to return.
	 * @return Returns the key corresponding to the given name.
	 */	
	public Key getKey(String keyName) {
		return keys.get(keyName);
	}

	/**
	 * Returns the foreign key corresponding to the given name. <br>
	 * <br>
	 * Returns null if non existing foreign key is requested.<br>
	 * 
	 * @param foreignKeyName The name of the foreign key to return.
	 * @return Returns the foreign key corresponding to the given name.
	 */	
	public ForeignKey getForeignKey(String foreignKeyName) {
		return foreignKeys.get(foreignKeyName);
	}
	
	public Set<String> getAllTableNames() {
		return new HashSet<String>(tables.keySet());
	}
	
	public Set<String> getAllKeyNames() {
		return new HashSet<String>(keys.keySet());
	}
	
	public Set<String> getAllForeignKeyNames() {
		return new HashSet<String>(foreignKeys.keySet());
	}

	//complex setters
	/**
	 * Adds a table to the database. <br>
	 * <br>
	 * Calls default HashMap behaviour when adding existing table name.<br>
	 * 
	 * @param table The table to add to the database
	 */
	public void addTable(Table table) {
		tables.put(table.getName(), table);
	}
	
	/**
	 * Adds a key to the database. <br>
	 * <br>
	 * Calls default HashMap behaviour when adding existing key name.<br>
	 * 
	 * @param key The key to add to the database
	 */
	public void addKey(Key key) {
		keys.put(key.getName(), key);
	}
	
	/**
	 * Adds a foreign key to the database. <br>
	 * <br>
	 * Calls default HashMap behaviour when adding existing foreign key name.<br>
	 * 
	 * @param foreignKey The foreign key to add to the database
	 * @throws DuplicateEntryException Thrown when the foreign key name already exists.
	 */
	public void addForeignKey(ForeignKey foreignKey) {
		foreignKeys.put(foreignKey.getName(), foreignKey);
	}
	
	/**
	 * Removes a table from the database. <br>
	 * <br>
	 * Directly calls the HashMap function remove(String objectKey).<br>
	 * 
	 * @param tableName The table to be removed from the database
	 */
	public void removeTable(String tableName) {
		tables.remove(tableName);
	}
	
	/**
	 * Removes a key from the database. <br>
	 * <br>
	 * Directly calls the HashMap function remove(String objectKey).<br>
	 * 
	 * @param keyName The key to be removed from the database
	 */
	public void removeKey(String keyName) {
		keys.remove(keyName);
	}
	
	/**
	 * Removes a foreign key from the database. <br>
	 * <br>
	 * Directly calls the HashMap function remove(String objectKey).<br>
	 * 
	 * @param foreignKeyName The foreign key to be removed from the database
	 */
	public void removeForeignKey(String foreignKeyName) {
		foreignKeys.remove(foreignKeyName);
	}
	
	
	/**
	 * Removes a set of keys from the database. <br>
	 * <br>
	 * Directly calls the HashMap function remove(String objectKey) for each key name in the set.<br>
	 * 
	 * @param keyNames The keys to be removed from the database
	 */
	public void removeKeys(Set<String> keyNames) {
		for(String keyName : keyNames) {
			keys.remove(keyName);
		}
	}
	
	/**
	 * Removes a set of foreign keys from the database. <br>
	 * <br>
	 * Directly calls the HashMap function remove(String objectKey) for each foreign key name in the set.<br>
	 * 
	 * @param foreignKeyNames The foreign keys to be removed from the database
	 */
	public void removeForeignKeys(Set<String> foreignKeyNames) {
		for(String foreignKeyName : foreignKeyNames) {
			foreignKeys.remove(foreignKeyName);
		}
	}
	
	/**
	 * In the case of a table object having it's name changed, call this method with the old name. <br>
	 * <br>
	 * To maintain database integrity it is advised to use this method instead of manual operations from outside this class.<br>
	 * 
	 * @param oldTableName
	 */
	public void refreshTableName(String oldTableName) {
		Table table = tables.get(oldTableName);
		if(!table.getName().equals(oldTableName)) { 	//If the table names are equal, the put() call makes no change and remove()
			tables.put(table.getName(), table);			//would get rid of the table entirely. The if() prevents this, and now
			tables.remove(oldTableName);				//the table never leaves the map as it is put() before being removed().
		}												//And it is important it never leaves the map, in case the app suddenly stops
	}
}
